/* ==========================================================================
   Story Player -- shared modal + mini-player for every "story" testimonial
   widget on the page (see StoryPlayer in js/testimonials-v2.js). Previously
   built as a runtime-injected <style> tag (StoryPlayer's old injectStyles()
   function, a plain array of CSS strings) -- moved to a real stylesheet so
   it loads with the page instead of on first click (the .rt-card__cta
   button below used to render unstyled until a story was opened once, since
   the old injectStyles() call only ran inside StoryPlayer.build()) and so
   it can be edited/diffed like normal CSS.

   Layout note: the modal's Listen/Read tabs and the mini-player's compact
   vs. full-bar look are both handled here by breakpoint, not branched in
   JS -- under 641px both panels + tabs behave as tabs (one visible at a
   time) and the mini-player is a small floating pill; at 641px+ both
   panels show side by side (tabs hidden) and the mini-player becomes a
   full-width bottom bar with volume/time visible. Same markup, same JS
   state, just restyled below.
   ========================================================================== */

:root {
  --rt-transition-modal: 220ms;
  --rt-transition-mini: 180ms;
}

.rt-card__cta {
  display: inline-block;
  font-family: "Barlow", sans-serif;
  font-size: 1rem;
  font-weight: 600;
  color: #fff;
  background: var(--color_a);
  border: none;
  border-radius: 999px;
  padding: 0.55rem 1.4rem;
  cursor: pointer;
  transition:
    background 0.2s linear,
    transform 0.15s ease;
}
.rt-card__cta:hover,
.rt-card__cta:focus {
  background: var(--color_p);
  transform: translateY(-1px);
}
.rt-card__cta:active {
  transform: translateY(0);
}

body.rt-modal-open {
  overflow: hidden;
}

/* ---- Modal (fades + slides up as a sheet; backdrop fades) ---- */
.rt-modal {
  position: fixed;
  inset: 0;
  z-index: 9999;
  font-family: "Barlow", Arial, sans-serif;
}
.rt-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  opacity: 0;
  transition: opacity var(--rt-transition-modal) ease;
}
.rt-modal--visible .rt-modal__backdrop {
  opacity: 1;
}
.rt-modal__sheet {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: 0;
  background: #fff;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  padding: 1rem 1.75rem calc(1.35rem + env(safe-area-inset-bottom));
  overflow: hidden;
  transform: translateY(24px);
  opacity: 0;
  transition:
    transform var(--rt-transition-modal) ease,
    opacity var(--rt-transition-modal) ease;
}
.rt-modal--visible .rt-modal__sheet {
  transform: translateY(0);
  opacity: 1;
}

/* Desktop: a real centered dialog, not a bottom sheet -- top/left 50% +
   transform is what actually centers it both axes; the entering/visible
   states each define their own full transform (translate doesn't merge
   across rules) so centering and the slide-in settle happen together
   instead of fighting. */
@media (min-width: 641px) {
  .rt-modal__sheet {
    position: absolute;
    top: 50%;
    left: 50%;
    right: auto;
    bottom: auto;
    width: 92vw;
    max-width: 960px;
    height: 82vh;
    max-height: 760px;
    margin: 0;
    border-radius: 20px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
    /* Roomier than the mobile sheet: this is a rounded 960px card
		   floating on a backdrop, where the mobile value read as cramped
		   against the corners. No safe-area inset needed here. Nothing in
		   this file bleeds past the sheet padding (no negative margins), and
		   the docked mini-player is a sibling of the modal, not a child, so
		   widening this only insets the sheet's own flex children. */
    padding: 1.75rem 3rem;
    transform: translate(-50%, -46%);
  }
  .rt-modal--visible .rt-modal__sheet {
    transform: translate(-50%, -50%);
  }
}

/* Top-right icon pair (replaces the old single center "down" chevron):
   pop-out-to-mini-player + an explicit full close, per the redesign
   mockup. Absolutely positioned over the sheet's own padding box --
   .rt-modal__sheet is already `position:absolute`, which is enough to
   anchor these. */
.rt-modal__topbar {
  position: absolute;
  top: 0.85rem;
  right: 1rem;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  z-index: 2;
}
.rt-modal__down,
.rt-modal__close {
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: #9a9a9a;
  font-size: 1rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  transition:
    background 0.15s linear,
    color 0.15s linear;
}
/* The minimise mark is an inline <svg> now, not a font glyph, so its size
   comes from the element instead of font-size. Kept a touch smaller than
   the close X's 1rem, same as the old rule did. */
.rt-modal__down svg {
  display: block;
  width: 17px;
  height: 17px;
}
.rt-modal__down:hover,
.rt-modal__close:hover {
  background: rgba(0, 0, 0, 0.06);
  color: var(--color_a);
}

/* Room under the topbar so a 2-line centered title never runs under the
   icons. Sheet-level flex order (see .rt-modal__sheet, display:flex) --
   mobile sequence is title -> photo -> subtitle -> tabs -> player, per the
   redesign mockup; reset back to the natural title/subtitle/tabs/body/
   player order at the desktop breakpoint below. Margin-bottom is 0 here on
   mobile (see the equal .rt-modal__sheet gap set at that breakpoint below)
   -- kept for desktop, which doesn't use that gap and still wants its own
   tighter title-to-subtitle spacing. */
.rt-modal__head {
  order: 1;
  text-align: center;
  flex: 0 0 auto;
  margin-bottom: 0.75rem;
  padding-top: 1.75rem;
}
.rt-modal__title {
  font-family: "Playfair Display", Georgia, serif;
  /* Kept modest on mobile specifically so a long title wrapping to two
	   lines (vs. a short one staying on one) doesn't swing the sheet's
	   overall content height as much -- desktop goes bigger (see
	   breakpoint below), where the row below it doesn't reflow the same
	   way. */
  font-size: 1.85rem;
  line-height: 1.25;
  color: var(--color_p);
  margin: 0;
  font-style: normal;
  /* It's an <h2>, so this has to be stated rather than omitted -- the UA
	   stylesheet would otherwise make it bold. */
  font-weight: normal;
}
.rt-modal__subtitle {
  order: 3;
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.5;
  color: #8a8a8a;
  text-align: center;
  flex: 0 0 auto;
}
.rt-modal__author {
  /* Own line above the reference (mockup shows "PIERRE" separate from
	   "Reference: ..."), not inline with it -- reset to inline at the
	   desktop breakpoint below, where both share one line like before. */
  display: block;
  margin-bottom: 0.3rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #6b6b6b;
}
.rt-modal__sep {
  /* The " · " joiner only makes sense on desktop's single-line subtitle
	   -- hidden here since .rt-modal__author is its own line on mobile
	   (shown again at the desktop breakpoint below). */
  display: none;
}

/* Tabs -- mobile only (hidden at desktop, see breakpoint below). Two
   standalone pill buttons (not a joined bar) per the mockup. */
.rt-modal__tabs {
  order: 4;
  display: flex;
  justify-content: center;
  gap: 0.6rem;
  flex: 0 0 auto;
  margin-bottom: 0.9rem;
}
.rt-modal__tab {
  border: none;
  border-radius: 999px;
  background: #eeece7;
  color: #8a8a8a;
  padding: 0.75rem 1.75rem;
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  transition:
    background 0.15s linear,
    color 0.15s linear;
}
.rt-modal__tab.is-active {
  background: var(--color_a);
  color: #fff;
}
/* The active tab already sits on --color_a, so it's excluded -- orange on
   orange would just erase the label. */
.rt-modal__tab:not(.is-active):hover {
  color: var(--color_a);
}

/* Panels + the desktop vertical volume fader share this row. */
.rt-modal__body {
  order: 2;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  gap: 0.75rem;
  margin-bottom: 0.6rem;
}
.rt-modal__panels {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
}
.rt-modal__panel {
  display: none;
  height: 100%;
  overflow-y: auto;
}
.rt-modal__panel.is-active {
  display: block;
}
/* Crossfade between Listen/Read on mobile. */
.rt-modal__panel {
  opacity: 0;
  transition: opacity 180ms ease;
}
.rt-modal__panel.is-active {
  opacity: 1;
}
/* Always fills its .rt-modal__panel exactly (100% x 100%) -- the box
   itself (roughly-square + centered on mobile, matched to the text panel
   on desktop) is sized by .rt-modal__panels/.rt-modal__panel instead, at
   each breakpoint below, so Listen and Read always share one size and
   switching tabs never changes the sheet's height. */
.rt-modal__photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 14px;
  transition: opacity 200ms ease;
}
.rt-modal--loading .rt-modal__photo {
  opacity: 0.4;
}
.rt-modal__text {
  font-size: 1.2rem;
  line-height: 1.5;
  color: var(--color_pd1, #333);
}
.rt-modal__text-error {
  color: #b3261e;
}
.rt-modal__text-loading {
  color: var(--color_pd1, #333);
  opacity: 0.6;
}
/* .rt-modal__text-lead intentionally has no rule: nothing renders that
   class anymore. It styled a lead paragraph that repeated the story title
   already shown in .rt-modal__title -- see renderCurrent(). */
/* Several story files in ../html/ wrap their body in <div class="quote">,
   and the site-wide main.css (loaded before this file) gives that a 92px
   "\201C" glyph via ::before, absolutely positioned over the text. That
   reads fine on a full web page but is far too heavy inside the modal's
   narrow Read panel, so it's suppressed here -- scoped to .rt-modal__text
   so the same class keeps its normal treatment anywhere else on the site.
   The 58px left padding goes with it: it existed only to clear the glyph,
   and left behind on its own it looks like a stray indent. */
.rt-modal__text .quote::before {
  content: none;
}
.rt-modal__text .quote p {
  padding-left: 0;
}

.rt-modal__loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.8rem;
  color: var(--color_a);
  opacity: 0;
  pointer-events: none;
  transition: opacity 150ms ease;
}
.rt-modal--loading .rt-modal__loading {
  opacity: 1;
}

.rt-modal__player {
  order: 5;
  flex: 0 0 auto;
}
.rt-modal__progress,
.rt-mini__progress {
  position: relative;
  height: 6px;
  border-radius: 3px;
  background: rgba(0, 0, 0, 0.1);
  cursor: pointer;
  margin-bottom: 0.3rem;
}
.rt-modal__progress-fill,
.rt-mini__progress-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0;
  border-radius: 3px;
  background: var(--color_a);
  transition: width 100ms linear;
}
/* Small round scrubber handle at the current position, like the mockup's
   capsule at the head of the fill. */
.rt-modal__progress-fill::after,
.rt-mini__progress-fill::after {
  content: "";
  position: absolute;
  right: -5px;
  top: 50%;
  transform: translateY(-50%);
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--color_a);
  box-shadow: 0 0 0 2px #fff;
}
.rt-modal__times,
.rt-mini__times {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  font-size: 0.7rem;
  color: #8a8a8a;
  margin-bottom: 0.5rem;
}
.rt-modal__times span,
.rt-mini__times span {
  min-width: 2.6em;
}
.rt-modal__times span:last-child,
.rt-mini__times span:last-child {
  text-align: right;
}

/* Transport buttons read as neutral gray in the mockup (only the play
   button stays brand gold) -- distinct from --color_p, which stays
   reserved for the title/links. Sizes bumped for the redesign -- the
   .rt-mini__controls overrides further down (and their !important on
   .rt-btn-play) re-assert the mini-player's own smaller scale, so this
   bump only affects the full modal. */
.rt-modal__controls,
.rt-mini__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.75rem;
}
.rt-modal__controls button,
.rt-mini__controls button {
  border: none;
  background: none;
  cursor: pointer;
  /* Never squash. Flex items default to flex-shrink:1, so on a narrow
	   phone the row would compress its children instead of overflowing --
	   .rt-btn-play, being the widest, turned into a flattened ellipse.
	   fitViewport() shrinks the gap between them instead. */
  flex: 0 0 auto;
  font-size: 1.6rem;
  padding: 0.25rem;
  position: relative;
  transition:
    opacity 0.15s linear,
    transform 0.1s ease,
    color 0.15s linear;
}
/* Split out of the shared rule above: the modal's transport icons take
   the same grey as its close/minimise pair (.rt-modal__close), and hover
   to the play button's orange. The docked mini-player keeps the darker
   grey it always had -- it's a different surface, and nothing was asked
   of it. */
.rt-modal__controls button {
  color: #9a9a9a;
}
.rt-mini__controls button {
  color: #4a4a4a;
}
.rt-modal__controls button:hover {
  color: var(--color_a);
}
.rt-mini__controls button:hover {
  color: #1a1a1a;
}
.rt-modal__controls button:active,
.rt-mini__controls button:active {
  transform: scale(0.9);
}
.rt-modal__controls button:disabled,
.rt-mini__controls button:disabled {
  opacity: 0.35;
  cursor: default;
}
.rt-btn-play {
  width: 5.5rem;
  height: 5.5rem;
  border-radius: 50%;
  background: var(--color_a) !important;
  color: #fff !important;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.9rem !important;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
  transition: filter 0.15s linear;
}
/* The one button a colour hover can't reach: its icon is #fff !important
   on a --color_a fill, i.e. it already *is* the hover colour. Darkened
   slightly instead so it still answers the pointer like its neighbours. */
.rt-btn-play:hover {
  filter: brightness(0.93);
}
.rt-btn-10 {
  display: flex;
  align-items: center;
  justify-content: center;
}
.rt-btn-10 svg {
  display: block;
}
/* Skip-10 SVGs carry their own width/height="20" attributes (set in JS,
   shared with the mini-player) -- scoped up here to keep the mini-player
   at its original compact size. */
.rt-modal__controls .rt-btn-10 svg {
  width: 28px;
  height: 28px;
}

/* Volume -- three placements share this markup/behavior:
   --h (mobile modal, own row under the transport controls),
   --v (desktop modal, vertical fader beside the text panel),
   --mini (the docked mini-player bar, desktop-only, unchanged from
   before). Each variant's visibility is set per breakpoint below instead
   of on the shared .rt-vol-group base. */
.rt-vol-group {
  align-items: center;
  gap: 0.5rem;
}
.rt-vol-group--h {
  display: flex;
  justify-content: center;
  margin-top: 0.75rem;
}
.rt-vol-group--h .rt-vol-slider {
  flex: 1 1 auto;
  max-width: 260px;
}
.rt-vol-group--mini {
  display: none;
}
/* Explicit reset, independent of any ancestor -- .rt-btn-vol used to only
   look right by accident, inheriting the button reset from being nested
   inside .rt-modal__controls in the old markup. Moving it into its own
   row/column (mobile/desktop volume groups) broke that: an unstyled
   native <button> shows its default box, worse once :active/:focus kick
   in on click. */
.rt-btn-vol {
  border: none;
  background: none;
  padding: 0.25rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #9a9a9a;
  font-size: 1rem;
}
.rt-btn-vol:hover {
  color: #4a4a4a;
}
/* Scoped to the modal on purpose: .rt-btn-vol above is shared with the
   docked mini-player, which is staying as it is. */
.rt-modal .rt-btn-vol:hover {
  color: var(--color_a);
}
/* The "filled" look: a gradient that's solid --color_a up to the current
   value and flat gray after it, like the fill bar under the audio
   progress bar. Driven by the --rt-vol-pct custom property (set from JS
   in syncVolumeUI) instead of raw style.background so both the WebKit
   track (which paints the input element's own background once
   -webkit-appearance:none is set) and Firefox's separate
   ::-moz-range-track pseudo-element -- which does NOT inherit inline
   styles from the host element, only real CSS custom properties -- pick
   it up the same way. */
.rt-vol-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 80px;
  height: 4px;
  border-radius: 2px;
  cursor: pointer;
  background: linear-gradient(
    to right,
    var(--color_a) var(--rt-vol-pct, 100%),
    rgba(0, 0, 0, 0.18) var(--rt-vol-pct, 100%)
  );
}
.rt-vol-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--color_a);
  cursor: pointer;
}
.rt-vol-slider::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border: none;
  border-radius: 50%;
  background: var(--color_a);
  cursor: pointer;
}
.rt-vol-slider::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(
    to right,
    var(--color_a) var(--rt-vol-pct, 100%),
    rgba(0, 0, 0, 0.18) var(--rt-vol-pct, 100%)
  );
}

/* ---- Mini-player: mobile = small floating pill ---- */
.rt-mini {
  position: fixed;
  left: 0.6rem;
  right: 0.6rem;
  bottom: 0.6rem;
  z-index: 9998;
  background: #fff;
  border-radius: 14px;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.6rem;
  font-family: "Barlow", Arial, sans-serif;
  transform: translateY(16px);
  opacity: 0;
  transition:
    transform var(--rt-transition-mini) ease,
    opacity var(--rt-transition-mini) ease;
}
/* Author CSS beats the browser's own `[hidden] { display: none }` rule
   regardless of specificity (it's a higher-priority origin, not a
   specificity fight) -- so the `display: flex` above silently cancelled
   out every `miniEl.hidden = true` the JS ever set (collapse/expand/close
   in testemonial-widget.js). The mini-player never actually left the page:
   it just sat there at opacity:0, still `position: fixed` and full-size,
   quietly eating clicks meant for whatever page content happened to be
   underneath it (confirmed live: the site footer, once the player had
   been opened and closed at least once). `.rt-mini[hidden]` has higher
   specificity than the plain `.rt-mini` rule above, so this wins and the
   element is actually removed from the layout -- no pointer-events hack
   needed instead, and nothing else about the animation changes since
   `hidden` is never toggled mid-transition, only once each is finished. */
.rt-mini[hidden] {
  display: none;
}
.rt-mini--visible {
  transform: translateY(0);
  opacity: 1;
}
/* Hidden everywhere -- both the mobile pill and the desktop bar now
   re-expand via clicking the artwork/title instead (wired in build(), see
   testimonials-v2.js). Left in the DOM rather than removed, in case a
   future layout wants the explicit chevron back. */
.rt-mini__up {
  display: none;
}
.rt-mini__thumb {
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 50%;
  object-fit: cover;
  flex: 0 0 auto;
}
.rt-mini__body {
  flex: 1 1 auto;
  min-width: 0;
}
.rt-mini__progress,
.rt-mini__times {
  display: none; /* shown at desktop breakpoint only */
}
.rt-mini__title {
  margin: 0;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--color_p);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rt-mini__subtitle {
  margin: 0;
  font-size: 0.7rem;
  color: #8a8a8a;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rt-mini__controls {
  gap: 0.5rem;
  flex: 0 0 auto;
}
.rt-mini__controls .rt-btn-play {
  width: 2.2rem;
  height: 2.2rem;
  font-size: 0.85rem !important;
}
.rt-mini__controls button {
  font-size: 0.9rem;
}
.rt-mini__close {
  border: none;
  background: none;
  color: #aaa;
  font-size: 1.3rem;
  line-height: 1;
  flex: 0 0 auto;
  cursor: pointer;
  padding: 0 0.15rem;
}
@media (max-width: 640px) {
  /* One consistent gap between every top-level block (title, photo,
	   subtitle, tabs, player) instead of each carrying its own
		 margin-bottom/margin-top -- those had drifted to different values
	   across rounds of tweaking (0.75rem / 1.6rem / 0 / 0.9rem between
	   different pairs), which read as uneven/broken spacing even though
	   each individual value was reasonable on its own. .rt-modal__sheet
	   is already the flex column all of them are direct children of (the
	   topbar is position:absolute, so it's not part of this flow and
	   unaffected), so a single `gap` here replaces all of that. */
  /* --rt-vh is the visible viewport height, written by fitViewport() on
	   open and on every visualViewport change. position:fixed + inset:0
	   resolves against the LAYOUT viewport, which on phones includes the
	   strip behind the collapsing URL bar -- so the sheet was taller than
	   the screen and its last row (the player) sat below the fold. The
	   100% fallback keeps this sane if JS never runs. */
  .rt-modal {
    height: var(--rt-vh, 100%);
  }
  /* Every child below is flex:0 0 auto (fixed to its own content), so on a
     tall phone the stack's natural height falls short of --rt-vh and, with
     the default flex-start, the leftover shows as dead white space under
     the player. Centering distributes it evenly above/below instead --
     confirmed live at 375x667 through 440x956, not just the wide end.
     No-op in Read mode: .rt-modal--reading .rt-modal__body below switches
     to flex:1 1 auto and claims all the leftover space itself, so there's
     nothing left for this to distribute. */
  .rt-modal__sheet {
    gap: 1rem;
    justify-content: center;
  }
  .rt-modal__head {
    margin-bottom: 0;
  }
  /* Fixed at exactly two lines' worth of height (1.85rem font-size *
	   1.25 line-height * 2, from the base rule above) and centered inside
	   that box -- so a one-line title ("Bearing witness") and a two-line
	   one ("How I became a Lourdes hospitalier") both reserve the same
	   space, instead of everything below sliding up/down depending on how
	   long this particular story's title happens to be. overflow:hidden
	   is a safety net for the rare title that would need a third line. */
  .rt-modal__title {
    height: 4.6rem;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
  }
  .rt-modal__body {
    margin-bottom: 0;
  }
  .rt-modal__tabs {
    margin-bottom: 0;
  }

  .rt-modal__progress {
    height: 9px;
  }
  .rt-modal__times {
    font-size: 0.85rem;
  }

  /* Listen and Read now share one fixed, aspect-ratio-driven box instead
	   of the photo sizing itself independently (via its own width/
	   aspect-ratio, removed above) while the text panel free-grew with
	   .rt-modal__body's flex:1 1 auto -- that mismatch was the root cause
	   of both bugs seen so far: a blank gap around the photo that varied
	   with how much room sibling text needed, AND a visible jump in sheet
	   height when switching Listen -> Read. Both panels now fill this
	   same box (height:100% + overflow-y:auto already on .rt-modal__panel
	   above handles a long Read story by scrolling *within* it, same
	   pattern as the desktop breakpoint). .rt-modal__body itself no
	   longer needs to grow to bound that scroll -- the box's own size
	   does it now -- so it shrinks to content on both tabs alike. */
  .rt-modal__body {
    flex: 0 0 auto;
  }
  .rt-modal__panels {
    /* Overrides the base flex:1 1 auto. .rt-modal__body is a flex row, so
	     flex-grow:1 stretched this to the row's full width and silently beat
	     BOTH the 82% here and the exact square fitViewport() writes inline
	     (measured: inline width 274px, rendered 334px). Growing has to be off
	     for the box to actually keep the proportion this rule asks for; the
	     auto margins still centre it, since auto margins absorb the free
	     space the item no longer takes. Read mode re-fills the row via its
	     own width:100% rule below, with the inline px cleared in JS. */
    flex: 0 0 auto;
    width: 82%;
    aspect-ratio: 1 / 1;
    margin: 0 auto;
    transition: width 320ms ease;
  }

  /* ---- Read mode: the transport block leaves, the text takes its room ----

	   Two things animate together off the .rt-modal--reading class that
	   switchTab() puts on .rt-modal:

	   1. .rt-modal__player slides down and fades. `max-height` is what
	      actually frees the layout space -- and overflow stays VISIBLE on
	      purpose, so the block is still drawn while its box measures zero
	      and the slide reads as motion rather than a clip. The sheet's own
	      overflow:hidden is what finally swallows it. The negative
	      margin-bottom cancels the 1rem row gap the sheet would otherwise
	      still reserve for a zero-height child.
	   2. .rt-modal__panels drops the fixed square it holds while Listening
	      (that square exists so photo and text share one size and the sheet
	      doesn't jump between tabs) and is handed flex:1 1 auto instead, so
	      it absorbs the height as the player gives it up.

	   Transitions live on the resting rules, not on the --reading ones, so
	   both directions animate -- on the --reading rule alone the way back
	   would snap. */
  .rt-modal__player {
    max-height: 24rem;
    /* Opacity runs nearly as long as the slide on purpose: the tabs above
		   travel downwards into the space this is vacating, so the two cross
		   paths mid-transition. Fading as it goes keeps that overlap from
		   reading as the player passing over the tabs. */
    transition:
      transform 320ms ease,
      opacity 300ms ease,
      max-height 320ms ease,
      margin-bottom 320ms ease;
  }
  .rt-modal--reading .rt-modal__player {
    /* A fixed length, NOT translateY(100%): percentage translates resolve
		   against the element's own height, and max-height is driving that to
		   zero in the same transition -- so 100% collapses to 0 and the block
		   never actually travels. 14rem clears its natural height (~11rem
		   measured: progress + times + controls + volume), putting it past the
		   sheet's bottom edge, where overflow:hidden takes over. */
    transform: translateY(14rem);
    opacity: 0;
    max-height: 0;
    margin-bottom: -1rem;
    /* Off-screen but still in the DOM -- keeps it out of the tab order
		   and unclickable while it's gone. */
    pointer-events: none;
  }
  .rt-modal__body {
    transition: flex-grow 320ms ease;
  }
  .rt-modal--reading .rt-modal__body {
    flex: 1 1 auto;
    min-height: 0;
  }
  .rt-modal--reading .rt-modal__panels {
    width: 100%;
    aspect-ratio: auto;
    height: 100%;
  }

  /* Volume -- the native <input type=range> from .rt-vol-group--h, which
	   is what every volume control in this file uses now (a horizontal
	   range input handles click/drag natively), restyled
	   as one filled pill: icon + track share a single rounded background,
	   per the mockup, instead of a thin bare slider next to a plain icon. */
  .rt-vol-group--h {
    background: rgba(0, 0, 0, 0.06);
    border-radius: 999px;
    padding: 0.5rem 1rem;
    width: 100%;
    max-width: 300px;
    margin-left: auto;
    margin-right: auto;
    overflow: hidden;
  }
  .rt-vol-group--h .rt-btn-vol {
    color: #6b6b6b;
  }
  .rt-vol-group--h .rt-vol-slider {
    height: 10px;
    border-radius: 999px;
  }
  .rt-vol-group--h .rt-vol-slider::-webkit-slider-thumb {
    width: 10px;
    height: 10px;
  }
  .rt-vol-group--h .rt-vol-slider::-moz-range-thumb {
    width: 10px;
    height: 10px;
  }

  /* ---- Mini-player (mobile): artwork gets its own full-height column and
	   the right column stacks title / subtitle / transport, per the supplied
	   mockup. Done with grid rather than by moving .rt-mini__controls inside
	   .rt-mini__body in build(): the two are siblings, and the desktop bar
	   still wants every one of them on a single row, so a markup change would
	   have had to be undone again at the other breakpoint. No visible expand
	   chevron here either -- see .rt-mini__up, hidden everywhere now. ---- */
  .rt-mini {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "thumb text"
      "thumb controls";
    align-items: center;
    column-gap: 0.9rem;
    row-gap: 0.4rem;
    border-radius: 22px;
    padding: 0.75rem 1rem;
  }
  .rt-mini__thumb {
    grid-area: thumb;
    width: 5.75rem;
    height: 5.75rem;
    border-radius: 16px;
  }
  .rt-mini__body {
    grid-area: text;
    /* Keeps the title clear of the absolutely-positioned close button. */
    padding-right: 1.25rem;
  }
  /* One line, clipped. minmax(0, 1fr) on the column above is what actually
	   lets this ellipsis: a grid track's default `auto` minimum is its
	   content's min-content width, so a long title would push the track wider
	   than the card instead of overflowing and being trimmed. */
  .rt-mini__title {
    font-size: 1rem;
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .rt-mini__subtitle {
    display: block;
    font-size: 0.75rem;
  }
  .rt-mini__controls {
    grid-area: controls;
    justify-content: flex-start;
    gap: 0.75rem;
  }
  .rt-mini__controls .rt-btn-play {
    width: 2.75rem;
    height: 2.75rem;
    font-size: 1.05rem !important;
  }
  .rt-mini__controls button {
    font-size: 1.1rem;
  }
  /* Out of the grid flow entirely so it sits in the card's own top-right
	   corner, as drawn, instead of taking a third column and squeezing the
	   text. .rt-mini is position:fixed, so it anchors this itself. */
  .rt-mini__close {
    position: absolute;
    top: 0.35rem;
    right: 0.6rem;
  }
}

/* ---- Desktop (641px+): side-by-side Listen/Read (no tabs), fixed-height
   photo/text panels (photo capped instead of stretching to match the text
   column -- long text scrolls internally via the .rt-modal__panel
   overflow-y:auto set above), full-width bottom-docked mini-player bar
   with volume/time. ---- */
@media (min-width: 641px) {
  .rt-modal__title {
    font-size: 2rem;
  }
  /* The base 1.2rem is sized for mobile, where the Read panel spans the
	   full sheet width; here it's one half-width column beside the photo,
	   so the same size would give a short, awkward measure. */
  .rt-modal__text {
    font-size: 1rem;
  }
  /* Subtitle used to be nested right inside .rt-modal__head, so their
	   spacing came from the title's own margin-bottom; now that it's a
	   sibling (see order reset below), .rt-modal__head's own margin-bottom
	   is what separates them instead. Pulled in from the earlier 0.4rem so
	   the subtitle reads as attached to the title. Note the visible gap
	   stays larger than this number: the title's 1.25 line-height on a
	   2.75rem font adds ~0.5rem of half-leading under the glyphs that no
	   margin here can remove -- drop the line-height too if it ever needs
	   to be tighter than this. */
  .rt-modal__head {
    margin-bottom: 0.15rem;
  }
  /* Resets the mobile order (title -> photo -> subtitle -> tabs ->
	   player) back to natural document order -- subtitle directly under
	   the title again, like the confirmed desktop mockup. */
  .rt-modal__subtitle {
    order: 2;
    margin: 0;
    font-size: 0.85rem;
  }
  .rt-modal__author {
    display: inline;
    margin-bottom: 0;
  }
  .rt-modal__sep {
    display: inline;
  }
  .rt-modal__tabs {
    order: 3;
    display: none;
  }
  .rt-modal__body {
    order: 4;
    /* Nothing separated this from the subtitle before: .rt-modal__tabs is
		   display:none here, so order 2 (subtitle) and order 4 (body) end up
		   visually adjacent, the subtitle carries margin:0, and the sheet's
		   `gap` is mobile-only -- the pair sat flush at 0. Set on the body
		   rather than the subtitle so it stays tied to the panels row, which
		   is the thing being pushed down. */
    margin-top: 1.25rem;
  }
  .rt-modal__panels {
    display: grid;
    grid-template-columns: 1fr 1fr;
    /* Column gap between the photo (Listen) and the text (Read) panel.
		   This is the only thing separating them -- the panels sit in this
		   grid, not in .rt-modal__body's flex, whose own 0.75rem gap goes
		   between this whole row and the vertical volume fader beside it. */
    gap: 2.5rem;
    /* Stretch (the grid default) on purpose now: .rt-modal__panel below
	   fills whatever height this row actually has rather than asking for
	   a fixed one, so there's nothing left to overflow. Confirmed live
	   that a fixed height + start here is what let the panel spill down
	   over .rt-modal__player on shorter windows -- .rt-modal__body's own
	   flex:1 1 auto already correctly accounts for the head/player rows
	   above and below it; 45vh/380px didn't know about either. */
  }
  .rt-modal__panel {
    display: block !important;
    opacity: 1 !important;
    /* Matches .rt-modal__body's actual available height instead of a
	   fixed 380px/45vh -- photo and text still read as the same size box
	   (both fill this), with the text scrolling internally via
	   overflow-y:auto (base .rt-modal__panel rule above) same as before. */
    height: 100%;
  }
  .rt-modal__panel--listen {
    display: block !important;
  }
  /* Square, centred in its column. Height leads and width follows it --
	   width:100% instead stretched the photo across whatever the 1fr column
	   happened to be, so its proportion changed with every sheet width.
	   max-width keeps a narrow column from pushing the image past its panel;
	   when that bites the square shrinks rather than breaking. */
  .rt-modal__photo {
    height: 100%;
    width: auto;
    max-width: 100%;
    aspect-ratio: 1 / 1;
    margin: 0 auto;
  }
  .rt-vol-group--mini {
    display: flex;
  }
  /* Positioning context for .rt-vol-group--h below. Without it that group
	   would anchor to .rt-modal__sheet (the nearest positioned ancestor)
	   and land in the sheet's bottom padding instead of on this row. */
  .rt-modal__player {
    position: relative;
  }
  /* The volume group rides the transport line here, pinned to its right
	   end, rather than sitting on a row of its own like it does on mobile.
	   Taken out of flow (.rt-modal__player is the positioning context, set
	   just above) on purpose: laid out in-flow next to .rt-modal__controls it
	   would push the five transport buttons off-centre by its own width,
	   and those need to stay centred in the sheet. Height matches
	   .rt-btn-play -- the tallest child of .rt-modal__controls, so it's
	   what sets that row's height -- which lets `align-items: center`
	   (inherited from .rt-vol-group) land this on the row's midline. */
  .rt-vol-group--h {
    display: flex;
    position: absolute;
    right: 0;
    bottom: 0;
    height: 5.5rem;
    width: auto;
    margin-top: 0;
  }
  .rt-vol-group--h .rt-vol-slider {
    flex: 0 0 auto;
    width: 96px;
  }

  .rt-mini {
    left: 0;
    right: 0;
    bottom: 0;
    border-radius: 0;
    box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.18);
    padding: 1rem 1.75rem;
    gap: 1.25rem;
    max-width: none;
  }
  .rt-mini__thumb {
    width: 5rem;
    height: 5rem;
    border-radius: 50%;
    cursor: pointer;
  }
  .rt-mini__body {
    display: flex;
    align-items: center;
    gap: 1.25rem;
  }
  .rt-mini__meta {
    flex: 0 0 auto;
    width: 260px;
    cursor: pointer;
  }
  .rt-mini__title {
    font-family: "Playfair Display", Georgia, serif;
    font-style: italic;
    font-size: 1.25rem;
  }
  .rt-mini__subtitle {
    display: block !important;
    font-size: 0.8rem;
  }
  /* Bug fixed here: this used to say `display:block` for both, which
	   silently killed .rt-mini__times's own `display:flex` (set in the
	   base rule above) since a later rule of equal specificity wins --
	   that's what was collapsing "0:02" and "2:02" into "0:022:02" with
	   no gap at all, `justify-content`/`gap` only do anything on a
	   flex/grid container. */
  .rt-mini__progress {
    display: block;
    flex: 1 1 auto;
    margin-bottom: 0.2rem;
  }
  .rt-mini__times {
    /* flex:0 (not 1) so the progress bar above gets the growing space --
		   this row shows only elapsed time in the docked bar (per the
		   mockup), not a start/end pair, so it doesn't need to stretch. */
    display: flex;
    flex: 0 0 auto;
  }
  .rt-mini__time-duration {
    display: none;
  }
  .rt-mini__controls {
    gap: 0.9rem;
  }
  .rt-mini__controls .rt-btn-play {
    width: 3.25rem;
    height: 3.25rem;
    font-size: 1.2rem !important;
  }
  .rt-mini__controls button {
    font-size: 1.15rem;
  }
  /* Same button + horizontal range pairing as the modal's volume group,
	   scaled down for the docked bar. Height is auto now (the old 60px was
	   there to give the vertical fader something to span) and gap is
	   tightened from the .rt-vol-group default, since this bar is short on
	   room next to the transport buttons. */
  .rt-vol-group--mini {
    margin: 0 0.5rem;
    gap: 0.35rem;
  }
  .rt-vol-group--mini .rt-vol-slider {
    flex: 0 0 auto;
    width: 72px;
  }
  .rt-vol-group--mini .rt-btn-vol {
    font-size: 0.95rem;
  }
  .rt-mini__close {
    font-size: 1.5rem;
  }
}

@media (min-width: 641px) and (max-height: 790px) {
  .rt-modal__sheet {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr);
    grid-template-rows: auto auto 1fr auto;
    grid-template-areas:
      "title title"
      "subtitle subtitle"
      "photo text"
      "photo player";
    column-gap: 1.75rem;
    row-gap: 0.4rem;
  }
  .rt-modal__head {
    grid-area: title;
  }
  .rt-modal__subtitle {
    grid-area: subtitle;
  }
  .rt-modal__body,
  .rt-modal__panels {
    display: contents;
  }
  .rt-modal__panel--listen {
    grid-area: photo;
    height: 100%;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    container-type: size;
  }
  .rt-modal__panel--read {
    grid-area: text;
    height: 100%;
    min-height: 0;
  }
  .rt-modal__player {
    grid-area: player;
  }
  .rt-modal__photo {
    width: 100cqmin;
    height: 100cqmin;
    aspect-ratio: 1 / 1;
    margin: 0;
  }
  .rt-vol-group--h .rt-vol-slider {
    display: none;
    position: absolute;
    left: 50%;
    bottom: calc(100% + 0.6rem);
    transform-origin: left center;
    transform: rotate(-90deg);
    width: 80px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
    border-radius: 999px;
    padding: 0.5rem 0.6rem;
  }
  .rt-vol-group--open .rt-vol-slider {
    display: block;
  }
}

@media (min-width: 641px) and (max-height: 799px) {
  .rt-modal__sheet {
    height: 94vh;
    max-height: none;
    padding: 1rem 2.5rem;
  }
  .rt-modal__title {
    font-size: 1.5rem;
  }
  .rt-modal__body {
    margin-top: 0.6rem;
  }
  .rt-btn-play {
    width: 3.75rem;
    height: 3.75rem;
    font-size: 1.3rem !important;
  }
  .rt-modal__controls {
    gap: 1rem;
  }
  .rt-modal__controls .rt-btn-10 svg {
    width: 22px;
    height: 22px;
  }
  .rt-vol-group--h {
    height: 3.75rem;
  }
}

.fa-spin {
  animation: rt-spin 1s linear infinite;
}
@keyframes rt-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
